﻿/* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 * '' Product        : Consilium
 * ''
 * '' Description    : Video Game
 * ''
 * '' Author         : Group #4
 * ''
 * '' Platforms      : This version is supported for Microsoft Windows
 * ''
 * ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 
 */
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;

using GameCommon;

namespace GameModel
{
    /// <summary>
    ///   The <c>Map</c> is a two-dimensional grid of <c>Tile</c>s.
    ///   <c>Map</c>s are constructed from *.map and *.scenario files.
    ///   A *.map file can only use <c>validTerrainCodes</c>.
    /// </summary>
    /// <remarks>
    ///   All public attributes (<c>width</c>, <c>height</c>, 
    ///   c>tiles</c>, <c>validTerrainCodes</c>) are immutable.
    /// </remarks>
    class Map
    {
        public const string validTerrainCodes = "gfm";
        public int width { get { return myWidth; } }
        public int height { get { return myHeight; } }
        public int numTeams { get { return myNumTeams; } }
        public readonly Tile[,] tiles;

        private int myWidth;
        private int myHeight;
        private string myTerrain;
        private int myNumTeams;

        /// <summary>
        ///   Yields a new Map using the files provided. Will close
        ///   program on failure to load or parse.
        /// </summary>
        /// <param name="mapFilename">
        ///   A file containing static things, like map size and terrain
        ///   codes.
        /// </param>
        /// <see cref="mapSyntax" />
        /// <param name="scenarioFilename">
        ///   An XML tree containing dynamic or variable things, like
        ///   teams, resources, and base locations.
        /// </param>
        /// <see cref="scenarioSyntax" />
        public Map(string mapFilename, XmlDocument scenarioDocument)
        {
            parseMap(mapFilename);
            tiles = new Tile[width, height];
            for (int x = 0, i = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                    tiles[x, y] = new Tile(terrainCodes[myTerrain[i++]], x, y);
            parseScenario(scenarioDocument);
        }

        /// <summary>
        ///   Extracts and sets the map's <c>width</c>, <c>height</c>,
        ///   and <c>myTerrain</c> from a given file. Will close the
        ///   program if the mapfile is malformatted or does not exist.
        /// </summary>
        /// <remarks name="mapSyntax">
        ///   <para>
        ///     Valid map syntax is rididly defined. Each file line
        ///     should represent a row in the <c>tiles</c> grid. Each
        ///     character must be in <c>validTerrainCodes</c>.
        ///     Example for a (6,3) map:
        ///   </para>
        ///   <code>
        ///     gmfggg
        ///     ggmfff
        ///     ffffmm
        ///   </code>
        /// </remarks>
        private void parseMap(string filename)
        {
            try
            {
                // Check that the *.map data exists, and store it.
                string[] lines = File.ReadAllLines(filename);
                if (lines.Length < 1 || lines[0].Length < 1)
                    throw new FormatException("map file is empty");
                myTerrain = String.Join("", lines);

                // Height is the number of lines; width is line length.
                myHeight = lines.Length;
                myWidth = lines[0].Length;

                // Check for (a) consistent width, (b) valid terrain codes.
                for (int i = 0; i < lines.Length; i++)
                    if (lines[i].Length != myWidth)
                        throw new FormatException(
                                       "Inconsistent line length in map file");
                    else if (invalidTerrain.IsMatch(lines[i]))
                        throw new FormatException(String.Format(
                           "Invalid terrain code in map file: line {0}", i+1));
            }
            catch (FileNotFoundException e)
            {
                Console.WriteLine(e.Message);
                Environment.Exit(1);
            }
            catch (FormatException e)
            {
                Console.WriteLine(e.Message);
                Environment.Exit(1);
            }
        }

        // TODO: parseScenario. XML Probably
        private void parseScenario(XmlDocument xmlDoc)
        {
            // Retrieve all team base locations and note them in the tiles.
            XmlNodeList teams = xmlDoc.GetElementsByTagName("team");
            try
            {
                myNumTeams = teams.Count;
                for (int i = 0; i < teams.Count; i++)
                {
                    int x = Convert.ToInt32(teams[i].Attributes["x"].Value);
                    int y = Convert.ToInt32(teams[i].Attributes["y"].Value);
                    int id = Convert.ToInt32(teams[i].Attributes["id"].Value);

                    tiles[x, y].resource = ResourceType.BaseZone;
                    tiles[x, y].owner = (TeamColor)id;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine("Failed to parse scenario file. Closing...");
                Environment.Exit(1);
            }
        }

        // File parsing resources.
        private static Regex invalidTerrain = new Regex(String.Format("[^{0}]",
                                                           validTerrainCodes));
        private static Dictionary<char, TerrainType> terrainCodes =
                       new Dictionary<char, TerrainType>()
                       {
                           {'g', TerrainType.Grass},
                           {'f', TerrainType.Forest},
                           {'m', TerrainType.Mountain}
                       };
    }
}
